package com.learn.service.impl;

import com.learn.entity.Users;
import com.learn.service.IUsersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;

/**
 * 该实现中，当发现缓存没有的时候，不再全部去数据库查询，
 * 而是cas选举出来一个线程，去加载数据库到缓存
 * 其他线程自旋等待。
 *
 *
 */
@Service
@Slf4j
@Primary
public class IUsersServiceBetterImpl implements IUsersService {

    @Autowired
    private RedisTemplate redisTemplate;

    private volatile int initControl;


    @Override
    public void deleteUser(long userId) {
        redisTemplate.delete(String.valueOf(userId));
        log.info("delete cache ok");
    }

    @Override
    public Users getUser(long userId) {
        ValueOperations<String, Users> ops = redisTemplate.opsForValue();

        Users users;
        while (true) {
            users = ops.get(String.valueOf(userId));
            if (users != null) {
                break;
            }

            int initControlLocal = initControl;
            /**
             * 如果已经有线程在进行获取了，则直接放弃cpu
             */
            if (initControlLocal < 0) {
//                log.info("initControlLocal < 0,just yield and wait");
                /**
                 * 这里可以不要这个，可以睡眠一小会。
                 */
//                Thread.yield();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    log.warn("e:{}", e);
                }
                continue;
            }


            /**
             * 争夺控制权
             */
            boolean bGotChanceToInit = U.compareAndSwapInt(this,
                    INIT_CONTROL, initControlLocal, -1);
            if (bGotChanceToInit) {
                try {
                    users = ops.get(String.valueOf(userId));
                    if (users == null) {
                        log.info("got change to init");
                        /**
                         * 这里要去查库获取值
                         */
                        users = getUsersFromDB(userId);
                        ops.set(String.valueOf(users.getUserId()), users);
                        log.info("init over");
                    }
                } finally {
                    initControl = 0;
                }

                break;
            }
        }


        return users;
    }

    private Users getUsersFromDB(long userId) {
        Users users = new Users();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("spent 1s to get user from db");
        users.setUserId(userId);
        users.setUserName("zhangsan");

        return users;
    }


    // Unsafe mechanics
    private static final sun.misc.Unsafe U;

    private static final long INIT_CONTROL;

    static {
        try {
//            U = sun.misc.Unsafe.getUnsafe();
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            U = (Unsafe) f.get(null);
            Class<?> k = IUsersServiceBetterImpl.class;
            INIT_CONTROL = U.objectFieldOffset
                    (k.getDeclaredField("initControl"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}
